// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2025 Linaro Ltd.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/auxiliary_bus.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/fwnode.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/idr.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/overflow.h>
#include <linux/printk.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>

#include "gpiolib.h"
#include "gpiolib-shared.h"

/* Represents a single reference to a GPIO pin. */
struct gpio_shared_ref {
	struct list_head list;
	/* Firmware node associated with this GPIO's consumer. */
	struct fwnode_handle *fwnode;
	/* GPIO flags this consumer uses for the request. */
	enum gpiod_flags flags;
	char *con_id;
	int dev_id;
	struct auxiliary_device adev;
	struct gpiod_lookup_table *lookup;
};

/* Represents a single GPIO pin. */
struct gpio_shared_entry {
	struct list_head list;
	/* Firmware node associated with the GPIO controller. */
	struct fwnode_handle *fwnode;
	/* Hardware offset of the GPIO within its chip. */
	unsigned int offset;
	/* Index in the property value array. */
	size_t index;
	struct mutex lock;
	struct gpio_shared_desc *shared_desc;
	struct kref ref;
	struct list_head refs;
};

static LIST_HEAD(gpio_shared_list);
static DEFINE_MUTEX(gpio_shared_lock);
static DEFINE_IDA(gpio_shared_ida);

#if IS_ENABLED(CONFIG_OF)
static struct gpio_shared_entry *
gpio_shared_find_entry(struct fwnode_handle *controller_node,
		       unsigned int offset)
{
	struct gpio_shared_entry *entry;

	list_for_each_entry(entry, &gpio_shared_list, list) {
		if (entry->fwnode == controller_node && entry->offset == offset)
			return entry;
	}

	return NULL;
}

/* Handle all special nodes that we should ignore. */
static bool gpio_shared_of_node_ignore(struct device_node *node)
{
	/*
	 * __symbols__ is a special, internal node and should not be considered
	 * when scanning for shared GPIOs.
	 */
	if (of_node_name_eq(node, "__symbols__"))
		return true;

	/*
	 * GPIO hogs have a "gpios" property which is not a phandle and can't
	 * possibly refer to a shared GPIO.
	 */
	if (of_property_present(node, "gpio-hog"))
		return true;

	return false;
}

static int gpio_shared_of_traverse(struct device_node *curr)
{
	struct gpio_shared_entry *entry;
	size_t con_id_len, suffix_len;
	struct fwnode_handle *fwnode;
	struct of_phandle_args args;
	struct property *prop;
	unsigned int offset;
	const char *suffix;
	int ret, count, i;

	if (gpio_shared_of_node_ignore(curr))
		return 0;

	for_each_property_of_node(curr, prop) {
		/*
		 * The standard name for a GPIO property is "foo-gpios"
		 * or "foo-gpio". Some bindings also use "gpios" or "gpio".
		 * There are some legacy device-trees which have a different
		 * naming convention and for which we have rename quirks in
		 * place in gpiolib-of.c. I don't think any of them require
		 * support for shared GPIOs so for now let's just ignore
		 * them. We can always just export the quirk list and
		 * iterate over it here.
		 */
		if (!strends(prop->name, "-gpios") &&
		    !strends(prop->name, "-gpio") &&
		    strcmp(prop->name, "gpios") != 0 &&
		    strcmp(prop->name, "gpio") != 0)
			continue;

		count = of_count_phandle_with_args(curr, prop->name,
						   "#gpio-cells");
		if (count <= 0)
			continue;

		for (i = 0; i < count; i++) {
			struct device_node *np __free(device_node) = NULL;

			ret = of_parse_phandle_with_args(curr, prop->name,
							 "#gpio-cells", i,
							 &args);
			if (ret)
				continue;

			np = args.np;

			if (!of_property_present(np, "gpio-controller"))
				continue;

			/*
			 * We support 1, 2 and 3 cell GPIO bindings in the
			 * kernel currently. There's only one old MIPS dts that
			 * has a one-cell binding but there's no associated
			 * consumer so it may as well be an error. There don't
			 * seem to be any 3-cell users of non-exclusive GPIOs,
			 * so we can skip this as well. Let's occupy ourselves
			 * with the predominant 2-cell binding with the first
			 * cell indicating the hardware offset of the GPIO and
			 * the second defining the GPIO flags of the request.
			 */
			if (args.args_count != 2)
				continue;

			fwnode = of_fwnode_handle(args.np);
			offset = args.args[0];

			entry = gpio_shared_find_entry(fwnode, offset);
			if (!entry) {
				entry = kzalloc(sizeof(*entry), GFP_KERNEL);
				if (!entry)
					return -ENOMEM;

				entry->fwnode = fwnode_handle_get(fwnode);
				entry->offset = offset;
				entry->index = count;
				INIT_LIST_HEAD(&entry->refs);
				mutex_init(&entry->lock);

				list_add_tail(&entry->list, &gpio_shared_list);
			}

			struct gpio_shared_ref *ref __free(kfree) =
					kzalloc(sizeof(*ref), GFP_KERNEL);
			if (!ref)
				return -ENOMEM;

			ref->fwnode = fwnode_handle_get(of_fwnode_handle(curr));
			ref->flags = args.args[1];

			if (strends(prop->name, "gpios"))
				suffix = "-gpios";
			else if (strends(prop->name, "gpio"))
				suffix = "-gpio";
			else
				suffix = NULL;
			if (!suffix)
				continue;

			/* We only set con_id if there's actually one. */
			if (strcmp(prop->name, "gpios") && strcmp(prop->name, "gpio")) {
				ref->con_id = kstrdup(prop->name, GFP_KERNEL);
				if (!ref->con_id)
					return -ENOMEM;

				con_id_len = strlen(ref->con_id);
				suffix_len = strlen(suffix);

				ref->con_id[con_id_len - suffix_len] = '\0';
			}

			ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
			if (ref->dev_id < 0) {
				kfree(ref->con_id);
				return -ENOMEM;
			}

			if (!list_empty(&entry->refs))
				pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n",
					 entry->offset, fwnode_get_name(entry->fwnode));

			list_add_tail(&no_free_ptr(ref)->list, &entry->refs);
		}
	}

	for_each_child_of_node_scoped(curr, child) {
		ret = gpio_shared_of_traverse(child);
		if (ret)
			return ret;
	}

	return 0;
}

static int gpio_shared_of_scan(void)
{
	if (of_root)
		return gpio_shared_of_traverse(of_root);

	return 0;
}
#else
static int gpio_shared_of_scan(void)
{
	return 0;
}
#endif /* CONFIG_OF */

static void gpio_shared_adev_release(struct device *dev)
{

}

static int gpio_shared_make_adev(struct gpio_device *gdev,
				 struct gpio_shared_entry *entry,
				 struct gpio_shared_ref *ref)
{
	struct auxiliary_device *adev = &ref->adev;
	int ret;

	lockdep_assert_held(&gpio_shared_lock);

	memset(adev, 0, sizeof(*adev));

	adev->id = ref->dev_id;
	adev->name = "proxy";
	adev->dev.parent = gdev->dev.parent;
	adev->dev.platform_data = entry;
	adev->dev.release = gpio_shared_adev_release;

	ret = auxiliary_device_init(adev);
	if (ret)
		return ret;

	ret = auxiliary_device_add(adev);
	if (ret) {
		auxiliary_device_uninit(adev);
		return ret;
	}

	pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n",
		 dev_name(&adev->dev), gpio_device_get_label(gdev));

	return 0;
}

#if IS_ENABLED(CONFIG_RESET_GPIO)
/*
 * Special case: reset-gpio is an auxiliary device that's created dynamically
 * and put in between the GPIO controller and consumers of shared GPIOs
 * referred to by the "reset-gpios" property.
 *
 * If the supposed consumer of a shared GPIO didn't match any of the mappings
 * we created when scanning the firmware nodes, it's still possible that it's
 * the reset-gpio device which didn't exist at the time of the scan.
 *
 * This function verifies it an return true if it's the case.
 */
static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
					  struct gpio_shared_entry *entry,
					  struct gpio_shared_ref *ref)
{
	struct fwnode_handle *reset_fwnode = dev_fwnode(consumer);
	struct fwnode_reference_args ref_args, aux_args;
	struct device *parent = consumer->parent;
	bool match;
	int ret;

	/* The reset-gpio device must have a parent AND a firmware node. */
	if (!parent || !reset_fwnode)
		return false;

	/*
	 * FIXME: use device_is_compatible() once the reset-gpio drivers gains
	 * a compatible string which it currently does not have.
	 */
	if (!strstarts(dev_name(consumer), "reset.gpio."))
		return false;

	/*
	 * Parent of the reset-gpio auxiliary device is the GPIO chip whose
	 * fwnode we stored in the entry structure.
	 */
	if (!device_match_fwnode(parent, entry->fwnode))
		return false;

	/*
	 * The device associated with the shared reference's firmware node is
	 * the consumer of the reset control exposed by the reset-gpio device.
	 * It must have a "reset-gpios" property that's referencing the entry's
	 * firmware node.
	 *
	 * The reference args must agree between the real consumer and the
	 * auxiliary reset-gpio device.
	 */
	ret = fwnode_property_get_reference_args(ref->fwnode, "reset-gpios",
						 NULL, 2, 0, &ref_args);
	if (ret)
		return false;

	ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios",
						 NULL, 2, 0, &aux_args);
	if (ret) {
		fwnode_handle_put(ref_args.fwnode);
		return false;
	}

	match = ((ref_args.fwnode == entry->fwnode) &&
		 (aux_args.fwnode == entry->fwnode) &&
		 (ref_args.args[0] == aux_args.args[0]));

	fwnode_handle_put(ref_args.fwnode);
	fwnode_handle_put(aux_args.fwnode);
	return match;
}
#else
static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
					  struct gpio_shared_entry *entry,
					  struct gpio_shared_ref *ref)
{
	return false;
}
#endif /* CONFIG_RESET_GPIO */

int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags)
{
	const char *dev_id = dev_name(consumer);
	struct gpio_shared_entry *entry;
	struct gpio_shared_ref *ref;

	struct gpiod_lookup_table *lookup __free(kfree) =
			kzalloc(struct_size(lookup, table, 2), GFP_KERNEL);
	if (!lookup)
		return -ENOMEM;

	guard(mutex)(&gpio_shared_lock);

	list_for_each_entry(entry, &gpio_shared_list, list) {
		list_for_each_entry(ref, &entry->refs, list) {
			if (!device_match_fwnode(consumer, ref->fwnode) &&
			    !gpio_shared_dev_is_reset_gpio(consumer, entry, ref))
				continue;

			/* We've already done that on a previous request. */
			if (ref->lookup)
				return 0;

			char *key __free(kfree) =
				kasprintf(GFP_KERNEL,
					  KBUILD_MODNAME ".proxy.%u",
					  ref->adev.id);
			if (!key)
				return -ENOMEM;

			pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n",
				 dev_id, key, ref->con_id ?: "none");

			lookup->dev_id = dev_id;
			lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0,
						       ref->con_id, lflags);

			gpiod_add_lookup_table(no_free_ptr(lookup));

			return 0;
		}
	}

	/* We warn here because this can only happen if the programmer borked. */
	WARN_ON(1);
	return -ENOENT;
}

static void gpio_shared_remove_adev(struct auxiliary_device *adev)
{
	lockdep_assert_held(&gpio_shared_lock);

	auxiliary_device_uninit(adev);
	auxiliary_device_delete(adev);
}

int gpio_device_setup_shared(struct gpio_device *gdev)
{
	struct gpio_shared_entry *entry;
	struct gpio_shared_ref *ref;
	unsigned long *flags;
	int ret;

	guard(mutex)(&gpio_shared_lock);

	list_for_each_entry(entry, &gpio_shared_list, list) {
		list_for_each_entry(ref, &entry->refs, list) {
			if (gdev->dev.parent == &ref->adev.dev) {
				/*
				 * This is a shared GPIO proxy. Mark its
				 * descriptor as such and return here.
				 */
				__set_bit(GPIOD_FLAG_SHARED_PROXY,
					  &gdev->descs[0].flags);
				return 0;
			}
		}
	}

	/*
	 * This is not a shared GPIO proxy but it still may be the device
	 * exposing shared pins. Find them and create the proxy devices.
	 */
	list_for_each_entry(entry, &gpio_shared_list, list) {
		if (!device_match_fwnode(&gdev->dev, entry->fwnode))
			continue;

		if (list_count_nodes(&entry->refs) <= 1)
			continue;

		flags = &gdev->descs[entry->offset].flags;

		__set_bit(GPIOD_FLAG_SHARED, flags);
		/*
		 * Shared GPIOs are not requested via the normal path. Make
		 * them inaccessible to anyone even before we register the
		 * chip.
		 */
		__set_bit(GPIOD_FLAG_REQUESTED, flags);

		pr_debug("GPIO %u owned by %s is shared by multiple consumers\n",
			 entry->offset, gpio_device_get_label(gdev));

		list_for_each_entry(ref, &entry->refs, list) {
			pr_debug("Setting up a shared GPIO entry for %s\n",
				 fwnode_get_name(ref->fwnode));

			ret = gpio_shared_make_adev(gdev, entry, ref);
			if (ret)
				return ret;
		}
	}

	return 0;
}

void gpio_device_teardown_shared(struct gpio_device *gdev)
{
	struct gpio_shared_entry *entry;
	struct gpio_shared_ref *ref;

	guard(mutex)(&gpio_shared_lock);

	list_for_each_entry(entry, &gpio_shared_list, list) {
		if (!device_match_fwnode(&gdev->dev, entry->fwnode))
			continue;

		list_for_each_entry(ref, &entry->refs, list) {
			gpiod_remove_lookup_table(ref->lookup);
			kfree(ref->lookup->table[0].key);
			kfree(ref->lookup);
			ref->lookup = NULL;
			gpio_shared_remove_adev(&ref->adev);
		}
	}
}

static void gpio_shared_release(struct kref *kref)
{
	struct gpio_shared_entry *entry =
		container_of(kref, struct gpio_shared_entry, ref);
	struct gpio_shared_desc *shared_desc;

	guard(mutex)(&entry->lock);

	shared_desc = entry->shared_desc;
	gpio_device_put(shared_desc->desc->gdev);
	if (shared_desc->can_sleep)
		mutex_destroy(&shared_desc->mutex);
	kfree(shared_desc);
	entry->shared_desc = NULL;
}

static void gpiod_shared_put(void *data)
{
	struct gpio_shared_entry *entry = data;

	lockdep_assert_not_held(&gpio_shared_lock);

	kref_put(&entry->ref, gpio_shared_release);
}

static struct gpio_shared_desc *
gpiod_shared_desc_create(struct gpio_shared_entry *entry)
{
	struct gpio_shared_desc *shared_desc;
	struct gpio_device *gdev;

	lockdep_assert_held(&entry->lock);

	shared_desc = kzalloc(sizeof(*shared_desc), GFP_KERNEL);
	if (!shared_desc)
		return ERR_PTR(-ENOMEM);

	gdev = gpio_device_find_by_fwnode(entry->fwnode);
	if (!gdev) {
		kfree(shared_desc);
		return ERR_PTR(-EPROBE_DEFER);
	}

	shared_desc->desc = &gdev->descs[entry->offset];
	shared_desc->can_sleep = gpiod_cansleep(shared_desc->desc);
	if (shared_desc->can_sleep)
		mutex_init(&shared_desc->mutex);
	else
		spin_lock_init(&shared_desc->spinlock);

	return shared_desc;
}

struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev)
{
	struct gpio_shared_desc *shared_desc;
	struct gpio_shared_entry *entry;
	int ret;

	lockdep_assert_not_held(&gpio_shared_lock);

	entry = dev_get_platdata(dev);
	if (WARN_ON(!entry))
		/* Programmer bug */
		return ERR_PTR(-ENOENT);

	scoped_guard(mutex, &entry->lock) {
		if (entry->shared_desc) {
			kref_get(&entry->ref);
			shared_desc = entry->shared_desc;
		} else {
			shared_desc = gpiod_shared_desc_create(entry);
			if (IS_ERR(shared_desc))
				return ERR_CAST(shared_desc);

			kref_init(&entry->ref);
			entry->shared_desc = shared_desc;
		}

		pr_debug("Device %s acquired a reference to the shared GPIO %u owned by %s\n",
			 dev_name(dev), gpiod_hwgpio(shared_desc->desc),
			 gpio_device_get_label(shared_desc->desc->gdev));
	}

	ret = devm_add_action_or_reset(dev, gpiod_shared_put, entry);
	if (ret)
		return ERR_PTR(ret);

	return shared_desc;
}
EXPORT_SYMBOL_GPL(devm_gpiod_shared_get);

static void gpio_shared_drop_ref(struct gpio_shared_ref *ref)
{
	list_del(&ref->list);
	kfree(ref->con_id);
	ida_free(&gpio_shared_ida, ref->dev_id);
	fwnode_handle_put(ref->fwnode);
	kfree(ref);
}

static void gpio_shared_drop_entry(struct gpio_shared_entry *entry)
{
	list_del(&entry->list);
	mutex_destroy(&entry->lock);
	fwnode_handle_put(entry->fwnode);
	kfree(entry);
}

/*
 * This is only called if gpio_shared_init() fails so it's in fact __init and
 * not __exit.
 */
static void __init gpio_shared_teardown(void)
{
	struct gpio_shared_entry *entry, *epos;
	struct gpio_shared_ref *ref, *rpos;

	list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) {
		list_for_each_entry_safe(ref, rpos, &entry->refs, list)
			gpio_shared_drop_ref(ref);

		gpio_shared_drop_entry(entry);
	}
}

static void gpio_shared_free_exclusive(void)
{
	struct gpio_shared_entry *entry, *epos;

	list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) {
		if (list_count_nodes(&entry->refs) > 1)
			continue;

		gpio_shared_drop_ref(list_first_entry(&entry->refs,
						      struct gpio_shared_ref,
						      list));
		gpio_shared_drop_entry(entry);
	}
}

static int __init gpio_shared_init(void)
{
	int ret;

	/* Right now, we only support OF-based systems. */
	ret = gpio_shared_of_scan();
	if (ret) {
		gpio_shared_teardown();
		pr_err("Failed to scan OF nodes for shared GPIOs: %d\n", ret);
		return ret;
	}

	gpio_shared_free_exclusive();

	pr_debug("Finished scanning firmware nodes for shared GPIOs\n");
	return 0;
}
postcore_initcall(gpio_shared_init);
